//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef _BUOLA_IO_CFIXEDMEMSTREAM_H_
#define _BUOLA_IO_CFIXEDMEMSTREAM_H_

#include <buola/io/cbufferstream.h>
#include <buola/iterator/range.h>

namespace buola { namespace io {

class CFixedMemStream : public CBufferStream
{
public:
    CFixedMemStream(const uint8_t *pBegin,const uint8_t *pEnd)
        :   mBegin(const_cast<uint8_t*>(pBegin))
        ,   mEnd(const_cast<uint8_t*>(pEnd))
    {
        mRPtr=mWPtr=mWEnd=mBegin;
        mREnd=mEnd;
    }

private:
    CResult DoRead(uint8_t *pData,size_t pLen) final
    {
        if(mREnd!=mEnd) //we were in write mode
        {
            mRPtr=mWPtr; //switch to read at same position
            mREnd=mEnd;
            mWPtr=mWEnd=mBegin;
            return Read(pData,pLen);
        }
        
        size_t lRead=mREnd-mRPtr;
        
        if(lRead) //copy what's available if something
        {
            memcpy(pData,mRPtr,lRead);
            mRPtr=mREnd;
        }
        
        return CResult(lRead,EResult::ENDOFFILE);
    }
    
    CResult DoWrite(const uint8_t *pData,size_t pLen) final
    {
        if(mWEnd!=mEnd) //we were in read mode
        {
            mWPtr=const_cast<uint8_t*>(mRPtr); //switch to write at same position
            mWEnd=mEnd;
            mRPtr=mREnd=mBegin;
            return Write(pData,pLen);
        }

        size_t lWritten=mREnd-mRPtr;
        
        if(lWritten) //copy what's available if something
        {
            memcpy(mWPtr,pData,lWritten);
            mWPtr=mWEnd;
        }
        
        ///\todo maybe we should just throw
        return CResult(lWritten,EResult::ENDOFFILE);
    }
    
    CResult DoIgnore(size_t pLen) final
    {
        if(mREnd!=mEnd) //we were in write mode
        {
            mRPtr=mWPtr; //switch to read at same position
            mREnd=mEnd;
            mWPtr=mWEnd=mBegin;
            return Ignore(pLen);
        }
        
        size_t lIgnored=mREnd-mRPtr;
        mRPtr=mREnd;
        return CResult(lIgnored,EResult::ENDOFFILE);
    }
    
    off_t DoSeek(off_t pPos,ESeek pMode) final
    {
        if(mREnd!=mEnd) //we are in write mode
        {
            uint8_t *lNewPtr;
            switch(pMode)
            {
            case ESeek::RELATIVE:
                lNewPtr=mWPtr+pPos;
                break;
            case ESeek::END:
                lNewPtr=mEnd+pPos;
                break;
            case ESeek::ABSOLUTE:
            default:
                lNewPtr=mBegin+pPos;
                break;
            }
            if(lNewPtr<mBegin||lNewPtr>mEnd)
                throw XInvalid("invalid seek");
            mWPtr=lNewPtr;
            return mWPtr-mBegin;
        }
        else //we are in read mode
        {
            const uint8_t *lNewPtr;
            switch(pMode)
            {
            case ESeek::RELATIVE:
                lNewPtr=mRPtr+pPos;
                break;
            case ESeek::END:
                lNewPtr=mEnd+pPos;
                break;
            case ESeek::ABSOLUTE:
            default:
                lNewPtr=mBegin+pPos;
                break;
            }
            if(lNewPtr<mBegin||lNewPtr>mEnd)
                throw XInvalid("invalid seek");
            mRPtr=lNewPtr;
            return mRPtr-mBegin;
        }
    }
    
    CResult DoUnderflow() final
    {
        if(mREnd!=mEnd) //we were in write mode
        {
            mRPtr=mWPtr;
            mREnd=mEnd;
            mWPtr=mWEnd=mBegin;
        }
        
        return CResult(mREnd-mRPtr,EResult::ENDOFFILE);
    }
    
    void DoFlush() final
    {
    }
    
    off_t DoTell() final
    {
        if(mREnd!=mEnd) //we are in write mode
        {
            return mWPtr-mBegin;
        }
        else //read mode
        {
            return mRPtr-mBegin;
        }
    }
    
protected:
    uint8_t *mBegin;
    uint8_t *mEnd;
};

typedef intrusive_ptr<CFixedMemStream> PFixedMemStream;

inline PFixedMemStream open_mem(const uint8_t *pBegin,const uint8_t *pEnd)
{
    return new CFixedMemStream(pBegin,pEnd);
}

inline PFixedMemStream open_mem(const CRange<const uint8_t*> &pRange)
{
    return new CFixedMemStream(pRange.first,pRange.second);
}

/*namespace io*/ } /*namespace buola*/ }

#endif
